/*
 * Copyright (c) 2025 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import {RuleListUtil} from "../../utils/common/DefectsList";
import {ArkFile, ts} from "arkanalyzer/lib";
import {AdviceChecker, BaseMetaData} from "../BaseChecker";
import {Defects, IssueReport} from "../../model/Defects";
import {AstMatcher, MatcherCallback, MatcherTypes} from "../../matcher/Matchers";
import {Rule} from "../../model/Rule";

interface Signature {
    name: string;
    isStatic: boolean;
}

export class AdjacentOverloadSignaturesCheck implements AdviceChecker {
    readonly metaData: BaseMetaData = {
        severity: 2,
        ruleDocPath: 'docs/adjacent-overload-signatures.md',
        description: 'All ${methodName} signatures should be adjacent.',
    };
    public rule: Rule;
    public defects: Defects[] = [];
    public issues: IssueReport[] = [];

    private containerMatcher: AstMatcher = {
        matcherType: MatcherTypes.AST,
        match: (node: ts.Node) => ts.isSourceFile(node) || ts.isModuleDeclaration(node) ||
            ts.isInterfaceDeclaration(node) || ts.isClassDeclaration(node) || ts.isTypeLiteralNode(node)
    };

    public registerMatchers(): MatcherCallback[] {
        return [{matcher: this.containerMatcher, callback: this.check}];
    }

    public beforeCheck(): void {
    }

    public afterCheck(): void {
    }

    public check = (node: ts.Node, file: ArkFile): void => {
        const members = this.getMembers(node);
        if (!members) {
            return;
        }
        const seen = new Map<string, ts.Node>();
        let last: Signature | null = null;

        for (const member of members) {
            const sig = this.extractSignature(member);
            if (!sig) {
                last = null;
                continue;
            }

            const key = `${sig.name}:${sig.isStatic}`;
            const prev = seen.get(key);

            if (prev && (!last || last.name !== sig.name || last.isStatic !== sig.isStatic)) {
                this.addIssue(member, node.getSourceFile(), file, sig.name);
            }

            if (!prev) {
                seen.set(key, member);
            }
            last = sig;
        }
    };

    private getMembers(node: ts.Node): ts.NodeArray<ts.Node> | undefined {
        if (ts.isSourceFile(node)) {
            return node.statements as ts.NodeArray<ts.Node>;
        }
        if (ts.isModuleDeclaration(node) && node.body && ts.isModuleBlock(node.body)) {
            return node.body.statements as ts.NodeArray<ts.Node>;
        }
        if (ts.isInterfaceDeclaration(node)) {
            return node.members as ts.NodeArray<ts.Node>;
        }
        if (ts.isClassDeclaration(node)) {
            return node.members as ts.NodeArray<ts.Node>;
        }
        if (ts.isTypeLiteralNode(node)) {
            return node.members as ts.NodeArray<ts.Node>;
        }
        return undefined;
    }

    private extractSignature(member: ts.Node): Signature | null {
        if (ts.isCallSignatureDeclaration(member)) {
            return {name: 'call', isStatic: false};
        }
        if (ts.isConstructorDeclaration(member)) {
            return {name: 'constructor', isStatic: false};
        }

        if (ts.isMethodSignature(member) || ts.isMethodDeclaration(member)) {
            if (member.modifiers?.some(m => m.kind === ts.SyntaxKind.AbstractKeyword)) {
                return null;
            }
            const name = this.getName(member.name);
            if (!name) {
                return null;
            }
            const isStatic = member.modifiers?.some(m => m.kind === ts.SyntaxKind.StaticKeyword) ?? false;
            return {name, isStatic};
        }

        if (ts.isFunctionDeclaration(member)) {
            const name = member.name?.text;
            return name ? {name, isStatic: false} : null;
        }

        return null;
    }

    private getName(name: ts.PropertyName | undefined): string | null {
        if (!name) {
            return null;
        }
        if (ts.isComputedPropertyName(name)) {
            const expr = name.expression;
            return (ts.isStringLiteral(expr) || ts.isIdentifier(expr)) ? expr.text : null;
        }
        if (ts.isIdentifier(name)) {
            return name.text;
        }
        if (ts.isPrivateIdentifier(name)) {
            return `#${name.text}`;
        }
        return null;
    }

    private addIssue(member: ts.Node, sourceFile: ts.SourceFile, file: ArkFile, name: string): void {
        const start = sourceFile.getLineAndCharacterOfPosition(member.getStart());
        const end = sourceFile.getLineAndCharacterOfPosition(member.getEnd());

        const defect = new Defects(
            start.line + 1,
            start.character + 1,
            end.character + 1,
            `All ${name} signatures should be adjacent.`,
            this.rule.alert ?? this.metaData.severity,
            this.rule.ruleId,
            file.getFilePath(),
            this.metaData.ruleDocPath,
            true,
            false,
            false
        );

        this.defects.push(defect);
        const issue = new IssueReport(defect, undefined);
        RuleListUtil.push(defect);
        this.issues.push(issue);
    }
}
